module necrophilia.animation.Inteligent;
import necrophilia.animation.Instance;
import necrophilia.helpers.Interpolation;
import necrophilia.animation.DataNodes;
import maths.Vec;
import maths.Quat;
import maths.CoordSys;
import utils.Memory;
import necrophilia.helpers.Callback;
import necrophilia.animation.Base;
import necrophilia.Time;
import necrophilia.skeleton.Sectors;

/**
	The philosophie here is to make Bone to do the least logic as possible. Why? Bone is a geometrical creation
    And shouldent be encoreged to do any logic. Skeleton is more like way of holding transformation tree.
	When looking at this from this point of view we need something to tell the bone, what transformation is it holding.
	Here comes the concept of InteligentAnimationInstance. It's not only giveing the frames, and timeline, but calculates
	real curent keyframe according to interpolation method we will give it.  That makes everything clean, bone is asking
	for the current frame and gets it calculated.
*/
class InteligentAnimationInstance(Tim):AnimationInstance!(Tim), dynamiclyInterpolated!(Tim.TimeType){
	alias Tim.TimeType Time;
	alias Tim TimerType;
	protected Interpolation!(Time) interpolation; 
	protected vec3[] vbufor;
	protected quat[] qbufor;
	protected int[] currents;
	protected bool on;
	
	Time lasttime=0;
	
	//TMP!
	int ident;
	
	alias Callback!(void, CoordSys, bool) DumpCallback;
	private DumpCallback dump[];
	
	void setDumpMethod(void delegate(CoordSys,bool) del, int boneIndex){
		dump[boneIndex].set(del);
	}

	void setDumpMethod(void function(CoordSys,bool) func, int boneIndex){
		dump[boneIndex].set(func);
	}	
	/**
		Constructor
		Params:
		animation=the animation of which this is instance 
	*/
	this(Animation!(Time) animation){
		static i=0;
		ident=i++;
		super(animation);
		currents.alloc(bonesNumber);
		dump.alloc(bonesNumber);
		on=false;
	}
	
	~this(){
		vbufor.free();
		qbufor.free();
		currents.free();
		dump.free();
	}
	
	void turnOn(){
		on=true;
	}
	
	void turnOff(){
		on=false;
	}
	
	bool isOn(){
		return on;
	}
	
	void setInterpolationMethod(vec3 delegate (Time time, vec3[] vecs...) del){
		interpolation.set(del);
	}
	
	void setInterpolationMethod(quat delegate (Time time, quat[] vecs...) del) {
		interpolation.set(del);
	}
	
	void setInterpolationMethod(vec3 function (Time time, vec3[] vecs...) func){
		interpolation.set(func);
	}
	
	void setInterpolationMethod(quat function (Time time, quat[] vecs...) func){
		interpolation.set(func);
	}
	
	void setOffsets(int[] offsets ...){
		interpolation.offsets=offsets.clone();
		vbufor.alloc(offsets.length);
		qbufor.alloc(offsets.length);
	}
	
	static if(is(Tim == Timer!(Time))){
		/**
			Updates the timer of animation instance. Additionally it updates the curent keyframe for each bone, 
			depending on the timer state. 
		*/
		override void update(){
			super.update();
			if(isPaused) return;
			if(isPlayingForward){
				foreach(index,inout current; currents){
					Keyframe!(Time)[]  keyframes=keyframes(index);
					if(keyframes.length==1){
						current=0;
						continue;
					}
					int second=getOffset(current,1,keyframes.length);
					while(time>keyframes[second].time) {
						current=second;
						second=getOffset(current,1,keyframes.length);
					}
				}
				return;
			}
			if(isPlayingBackward){
				foreach(index,inout current; currents){
					Keyframe!(Time)[]  keyframes=keyframes(index);
					if(keyframes.length==1){
						current=0;
						continue;
					}
					int second=getOffset(current,1,keyframes.length);
					while(time<keyframes[second].time) {
						current=second;
						second=getOffset(current,1,keyframes.length);
					}
				}
			}
		}
	} else static if(is(Tim : LoopTimer!(Time))){
		override void update(){
			//writefln(keyframes(0).length);
			super.update();
			if(isPaused) return true;
			if(isStoped) return true;
			if(isPlayingForward || isPlayingForwardLoop){
				foreach(index,inout current; currents){
					if(time<lasttime) current=0; 
					//writefln(current," ",time," ", index);
					Keyframe!(Time)[]  keyframes=keyframes(index);
					if(keyframes.length==1){
						current=0;
						continue;
					}
					
					int second=getOffset(current,1,keyframes.length);
					while(time>keyframes[second].time) { // HACK!
						//writefln(time," ", current);
						current=second;
						if(second==0) break;
						second=getOffset(current,1,keyframes.length);
					}
				}
				lasttime=time;
				return true;
			}
			if(isPlayingBackward || isPlayingBackwardLoop){
				foreach(index,inout current; currents){
					Keyframe!(Time)[]  keyframes=keyframes(index);
					if(keyframes.length==1){
						current=0;
						continue;
					}
					int second=getOffset(current,-1,keyframes.length);
					while(time<keyframes[second].time) {
						current=second;
						second=getOffset(current,-1,keyframes.length);
					}
				}
				return true;
			}
		}
	} else {
		override void update(){
			super.update();
			foreach(index,inout current; currents){
				Keyframe!(Time)[]  keyframes=keyframes(index);
				if(keyframes.length==1){
						current=0;
						continue;
				}
				int second=getOffset(current,-1,keyframes.length);
				while(time>keyframes[second].time) {
					current=second;
					second=getOffset(current,-1,keyframes.length);
				}
			}
		}
	}
	
	CoordSys getMyPosition(int boneIndex){
		int current=currents[boneIndex];
		Keyframe!(Time)[] frames= keyframes(boneIndex);
		if(frames.length==1) return frames[0].transformation;
		foreach(index,offset;interpolation.offsets){
			vbufor[index]=vec3.convert(frames[getOffset(current , offset, frames.length)].transformation.origin);
			qbufor[index]=frames[getOffset(current , offset, frames.length)].transformation.rotation;
		}
		
		Time interpolatedTime;
		interpolatedTime= frames[getOffset(current, interpolation.offsets[interpolation.offsets.length-1], frames.length)].time;
		interpolatedTime-=frames[getOffset(current, interpolation.offsets[0], frames.length)].time;
		
		static if(is(Tim : LoopTimer!(Time))){
			while(interpolatedTime<min) interpolatedTime+=(min-max);
		}
		
		Time interpolatedPointInTime=time-frames[getOffset(current, interpolation.offsets[0], frames.length)].time;
		Time toapp=(interpolatedPointInTime==interpolatedTime)?1:interpolatedPointInTime/interpolatedTime;
		
		//writefln(boneIndex, " ", toapp," ", interpolatedTime, " ", interpolatedPointInTime );
		CoordSys result;
		//if(boneIndex==1) {
			//writefln(ident,":", interpolatedPointInTime, " ",interpolatedTime);
			//writefln(ident,":", time, " ", frames[getOffset(current, interpolation.offsets[0], frames.length)].time," offset:", current);
			//writefln(ident,":", toapp," ",vbufor);
		//}
		result.origin=vec3fi.convert(interpolation.interpolate(toapp,vbufor));
		
		result.rotation=interpolation.interpolate(toapp,qbufor);
		
		//result.origin=frames[current].transformation.origin;
		//result.rotation=frames[current].transformation.rotation;
		//result.rotation=quat.slerp(frames[current].transformation.rotation,frames[current].transformation.rotation, 0.5);
		//result.origin=
		return result;
	}
	
	protected int getOffset(int current, int offset, int length){
		return (((current+offset)%length)+length)%length;
	}
	
	void getStandardFirst(){
		foreach(boneIndex, dum; dump){
			informationAboutTheFirst.call(boneIndex,firstNested[boneIndex]);
		}
	}
	
	void work(){
		if(!on) return;
		
		update();
		foreach(boneIndex, dum; dump){
			bool isAnimation= keyframes(boneIndex).length>2;
			dum.call(getMyPosition(boneIndex),isAnimation);
			if(isAnimation) informationAboutTheFirst.call(boneIndex,firstNested[boneIndex]); 
		}
	}
	
	 // HACK!
	alias Callback!(void, int, CoordSys) InformationCallback;
	protected CoordSys[] firstNested() {
		return _animation.firstNested;
	}
	protected InformationCallback informationAboutTheFirst;
	
	void set(void delegate (int, CoordSys) del){
		informationAboutTheFirst.set(del);
	}
	
	void set(void function(int, CoordSys) func){
		informationAboutTheFirst.set(func);
	}
	
	CoordSys getFirst(int boneIndex){
		Keyframe!(Time)[] frames= keyframes(boneIndex);
		return frames[0].transformation;
	}
	
	void setFirst(int boneIndex, CoordSys s){
		firstNested[boneIndex]=s;
	}
	
	bool isCalculated(){
		return _animation.isCalculated;
	}
	
	void setCalculated(){
		_animation.isCalculated=true;
	}
	
	char[][] sectors;
	
	void freeSectors(SectoredView!(typeof(this)) sectors){
		sectors.free(this.sectors);
		turnOff;
		setTime(0); // HACK ! should be Timer.begon!!!
	}
}

version(Unittest){
	
}

unittest {
	version(Unittest)
	{
		Animation!(float) animation=new Animation!(float)(1, 0.01, 0);
		animation.addKeyframe(0,0.4,CoordSys.identity);
		auto anim=new InteligentAnimationInstance!(LoopTimer!(float))(animation);
		anim.setNewBorders(0,1);
		anim.update();
		
	}
}

import std.stdio;